import time
import psutil
import pythoncom
from threading import Lock
import xlwings as xw
import win32timezone  # 打包问题显式导入模块
import pywintypes
import win32com.client as win32
import logging
logger = logging.getLogger(__name__)
# 1、线程退出时释放
# 每个线程在调用 CoInitialize() 初始化 COM 库后，必须在同一线程结束时调用 CoUninitialize()。
# 规则：CoInitialize() 和 CoUninitialize() 必须在同一线程内成对调用，且调用次数需匹配。
# 2、确保异常安全
# 即使代码抛出异常，也要确保 CoUninitialize() 被调用。使用 try...finally 或 with 上下文管理器保证释放。

pidlist = []
clipboard_lock = Lock()
def win32_com_init(pid):
    global pidlist
    if pid not in pidlist:
        pidlist.append(pid)
        logger.debug(f'start_win32_process {pid}')
        pythoncom.CoInitialize()  # 每个线程独立初始化

def win32_com_deinit(pid):
    global pidlist
    if pid == 'all':
        for one in pidlist:
            logger.debug(f'all quit_win32_process {pid}')
            pythoncom.CoUninitialize()  # 每个线程独立初始化
        pidlist = []

    elif pid in pidlist:
        pidlist.remove(pid)
        logger.debug(f'quit_win32_process {pid}')
        pythoncom.CoUninitialize()  # 每个线程独立初始化
    
def win32_cell_paste(oldcell, newcell):
    with clipboard_lock:  # 加锁
        oldcell.Copy()  # 复制源格式
        newcell.PasteSpecial(Paste=win32.constants.xlPasteFormats)

def check_all_process():
    # 遍历所有进程
    for proc in psutil.process_iter(['exe', 'pid', 'name']):
        proc_name = proc.info['name'].lower()  # 统一转为小写
        if proc.info['exe'] == None or proc.info['exe'].find('office') <= 0:
            continue
        logger.info(f"[*] {proc_name} PID: {proc.info['pid']}")

def kill_excel_process():
    for proc in psutil.process_iter():
        if proc.name() == "EXCEL.EXE":
            proc.kill()

def kill_word_process():
    for proc in psutil.process_iter():
        if proc.name() == "WORD.EXE":
            proc.kill()

def win32_safe_call(func, max_retries=3):
    for _ in range(max_retries):
        try:
            return func()
        except Exception as e:
            logger.error(f"调用{func.__name__}失败，重试中: {e}")
            time.sleep(1)
    check_all_process()
    kill_excel_process()
    kill_word_process()
    # raise Exception(f"调用{func.__name__}失败，已达最大重试次数")

class Win32AppManager:
    def open_office_or_wps(self, app_type='word'):
        office_names = {
            'word': ['Word.Application', 'kwps.Application'],  # Office Word → WPS 文字
            'excel': ['Excel.Application', 'ket.Application'],  # Office Excel → WPS 表格
            'ppt': ['PowerPoint.Application', 'kwpp.Application']  # Office PPT → WPS 演示
        }
        app = None
        for prog_id in office_names[app_type]:
            try:
                app = win32.DispatchEx(prog_id)
                logger.debug(f"成功启动: {prog_id}")
                break  # 成功则跳出循环
            except pywintypes.com_error as e:
                logger.debug(f"尝试 {prog_id} 失败: {e}")
                continue
        if not app:
            raise Exception("未找到 Office 或 WPS 组件")
        return app
    
    def __init__(self, file_path=None, opttype=None):
        self.app = None
        self.workbook = None
        self.opttype = opttype
        self.file_path = file_path

    def __enter__(self):
        # 选择win32的app
        if self.file_path.endswith('.xlsx'):
            if self.opttype == 'xw':
                self.app = xw.App(visible=False)  # 后台运行Excel
                self.app.ScreenUpdating = False  # 禁用屏幕刷新
                self.app.EnableEvents = False   # 禁用事件触发
                self.workbook = self.app.books.open(self.file_path)
            else:
                # Dispatch 打包后无法正常加载vba， 要用gencache.EnsureDispatch
                self.app = self.open_office_or_wps('excel')
                time.sleep(1)  # 等待 Excel 初始化
                self.app.Visible = False       # 隐藏界面
                self.app.ScreenUpdating = False  # 禁用屏幕刷新
                self.app.EnableEvents = False   # 禁用事件触发
                self.workbook = self.app.Workbooks.Open(self.file_path)
        elif self.file_path.endswith('.docx'):
            # 使用 DispatchEx 而非 Dispatch，确保实例隔离
            self.app = self.open_office_or_wps('word')
            time.sleep(1)  # 等待 Word 初始化
            self.app.Visible = False       # 隐藏界面
            self.app.ScreenUpdating = False  # 禁用屏幕刷新
            self.workbook = self.app.Documents.Open(self.file_path)
        else:
            return None
        
        time.sleep(0.5)  # 等待 
        logger.debug(f"[open] {self.file_path} ")
        return (self.app, self.workbook)

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.workbook:
            if self.opttype == 'xw':
                self.workbook.close()
            else:
                self.workbook.Close(SaveChanges=False)
            logger.debug(f"[close] {self.file_path} ")
        del self.workbook
        if self.app:
            if self.opttype == 'xw':
                self.app.quit()
            else:
                self.app.Quit()
        del self.app
     
